/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */

/**
 * Create a new Component.
 *
 * @class
 * @param url {string} The URL of this component
 * @param state {object} The Component state: must include 'id'.
 */
BHF.Component = function( url, state )
{
    BHF.Component.superclass.constructor.call( this, url );

    var useServerCache = true;

    this.state = state;
    if( BHF.lang.isUndefined( this.state.version ) )
        this.state.version = 0;

    this.events={};

    this.setUseServerCache = function( use )
    {
        useServerCache = use;
    };

    this.isUseServerCache = function()
    {
        return useServerCache;
    };

    this.updateState = function( o )
    {
        var state = o.state;

        // Update state only if we have a version change. Note that we might receive only a partial state
        // update, perhaps only those properties that have been changed, for efficiency. Thus, we can't simply
        // replace the entire state and have to overlay the changes.
        if( state && state.version != this.state.version )
        {
            for( var k in state ) if( BHF.lang.hasOwnProperty( state, k ) )
            {
                if( !BHF.lang.isFunction( state[k] ) )
                    this.state[k] = state[k];
            }
        }
    };

    this.fireEvents = function( o )
    {
        var events = o.events;

        if( events )
        {
            for( var i = 0, l = events.length; i < l; i++ )
            {
                a = BHF.lang.isString( events[i].o ) ? BHF.lang.JSON.parseJSON( events[i].o ) : events[i].o;
                if( BHF.lang.isArray( a ) )
                    this.events[events[i].event].fire.apply( this.events[events[i].event], a );
                else
                    this.events[events[i].event].fire.call( this.events[events[i].event], a );
            }
        }
    };
};
BHF.lang.extend( BHF.Component, BHF.Service );
/**
 * This method <strong>MUST</strong> be called after you have made any changes to the Component's
 * state. The version number will be incremented. Failure to do this could cause the Component to
 * revert to a previous state when making a server side call.
 * @public
 */
BHF.Component.prototype.changed = function()
{
    this.state.version++;
};
/**
 * Add a Custom Event to this component. The event will be added to the Component's <code>events</code> property.
 * @param name {string} Name of this event. Must be a valid identifier.
 * @public
 */
BHF.Component.prototype.addEvent = function( name )
{
    this.events[name] = new BHF.util.CustomEvent( name );
};
/**
 * Add a method to the Component
 * @param name {string} this method. Must be a valid identifier.
 * @public
 */
BHF.Component.prototype.addMethod = function( name )
{
    var that = this;

    that[name] = function( callback, parameter )
    {
        var adapter = BHF.Object( callback );
        var inline = this.isInline( callback, arguments );

        if( !inline ) // In particular, invokeSync doesn't know how to retry with state on a 491
        {
            that.agumentWithDefaults( callback );

            adapter.success = function( o, xhr )
            {
                that.updateState( o );

                if( BHF.lang.isFunction( callback.success ) )
                    callback.success( o.result, xhr );

                that.fireEvents( o );
            };

            adapter.failure = function( o, xhr )
            {
                // Retry with full state information - server side cache miss
                if( xhr.status == 491 )
                {
                    return that.invoke(
                        name,
                        adapter,
                        {
                            a: parameter,
                            v: that.state.version,
                            s: that.state
                        }
                    );
                }
                else if( BHF.lang.isFunction( callback.failure ) )
                {
                    callback.failure( o, xhr );
                }
            };
        }

        var params =
        {
            a: parameter,
            v: function() { return that.state.version } // We use a function to return the version so that it can be part of the precondition check
        };
        if( !that.isUseServerCache() ) // Do we need to send all the state on the first attempt? Yes, if not server side caching
            params.s = that.state
        if( inline )
            params.a = arguments[0]; // Inline only has a single argument

        if( BHF.lang.isDefined( that.token ) )
            params.t = that.token;

        if( inline )
        {
            var o = that.invokeSync( name, params );

            that.updateState( o );
            that.fireEvents( o );

            return o.result;
        }
        else
        {
            return that.invoke( name, adapter, params );
        }
    };
};
/**
 * Predefined functions that you can pass as your precondition callback.
 * @private
 */
BHF.Component.PRECONDITIONS =
{
    /**
     * The method argument must resolve now to the same value as before the call. They can only differ if
     * a function was passed instead of a value. Pass as the <code>precondition</code> property of your callback.
     * @public
     */
    SAME_ARG: function( txn )
    {
        var resolved = txn.service.resolve( txn.parameters );
        var k = 'a';

        return txn.resolved[k] === resolved[k];
    },

    /**
     * The component state must resolve to the same value
     * as before. This check is made with faith in the version number: i.e. just the version numbers are
     * compared. Pass as the <code>precondition</code> property of your callback.
     * @public
     */
    SAME_STATE: function( txn )
    {
        var resolved = txn.service.resolve( txn.parameters );
        var k = 'v';

        return txn.resolved[k] === resolved[k];
    },

    /**
     * The method argument must resolve now to the same value as before the call. They can only differ if
     * a function was passed instead of a value. In addition, the component state must resolve to the same value
     * as before. This check is made with faith in the version number: i.e. just the version numbers are
     * compared. Pass as the <code>precondition</code> property of your callback.
     * @public
     */
    SAME_STATE_ARG: function( txn )
    {
        var resolved = txn.service.resolve( txn.parameters );
        var k1 = 'a', k2 = 'v';

        return txn.resolved[k1] === resolved[k1] && txn.resolved[k2] === resolved[k2];
    }
};